package com.pixelengine.Task26Oee;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.pixelengine.HBasePeHelperCppConnector;
import com.pixelengine.TileComputeResult;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Timestamp;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

@Controller
public class WmtsController {
    ReentrantLock scriptContentLock = new ReentrantLock();
    //Caches
    Cache<String, TileComputeResult> m_tileCache = Caffeine.newBuilder()
            .maximumSize(TheConfig.getInstance().cache_tile_count)
            .expireAfterWrite(1, TimeUnit.DAYS )
            .build();
    Cache<String, String> m_scriptCache = Caffeine.newBuilder()
            .maximumSize(100)
            .expireAfterWrite(1, TimeUnit.DAYS )
            .build();

    public String getScriptContent(String fullName) {
        String scriptContent = m_scriptCache.getIfPresent(fullName) ;
        if( scriptContent==null) {
            if (Files.exists(Paths.get(fullName)) == false) {
                return null ;
            }else{
                try {
                    scriptContentLock.lock();
                    scriptContent = new String(
                            Files.readAllBytes(Paths.get(fullName)),
                            StandardCharsets.UTF_8);
                    m_scriptCache.put(fullName,scriptContent);
                    return scriptContent;
                }catch (Exception ex) {
                    if(scriptContentLock.isLocked()) scriptContentLock.unlock();
                    return null ;
                }
                finally {
                    if(scriptContentLock.isLocked()) scriptContentLock.unlock();
                }
            }
        }else{
            return scriptContent ;
        }
    }

    @ResponseBody
    @RequestMapping(value = "/{scriptname}/wmts/WMTSCapabilities.xml", method = RequestMethod.GET)
    @CrossOrigin(origins = "*")
    public ResponseEntity<byte[]> getCaps(@PathVariable("scriptname") String scriptname ) {
        boolean debuginfo = TheConfig.getInstance().debuginfo > 0;
        if(debuginfo) System.out.println("getCaps " + scriptname);
        String scriptfullname = TheConfig.getInstance().script_dir + scriptname + ".js";
        String scriptContent = getScriptContent(scriptfullname) ;
        if( scriptContent==null) {
            if(debuginfo) System.out.println("Error : script wmts no script file " + scriptfullname);
            final HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.TEXT_PLAIN);
            return new ResponseEntity<byte[]>(
                    (new String("getCaps no script file ")+scriptfullname).getBytes() ,
                    headers,
                    HttpStatus.NOT_FOUND ) ;
        }
        String temContent = TheConfig.getInstance().script_wmts_template_content ;
        try{
            String xmlContent2 = temContent.replace("{httproot}", TheConfig.getInstance().http_root);
            xmlContent2 = xmlContent2.replace("{scriptname}", scriptname );
            //return xml
            final HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.TEXT_XML);
            return new ResponseEntity<byte[]>(xmlContent2.getBytes(), headers, HttpStatus.OK);
        }catch (Exception ex ){
            if(debuginfo) System.out.println("Error : getCaps exception .");
            final HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.TEXT_PLAIN);
            return new ResponseEntity<byte[]>(
                    ("getCaps exception "+ex.getMessage()).getBytes(), headers, HttpStatus.NOT_FOUND ) ;
        }
    }


    //这个方法输出png瓦片图片
    @ResponseBody
    @RequestMapping(value="/{scriptname}/wmts/",method= RequestMethod.GET)
    @CrossOrigin(origins = "*")
    public ResponseEntity<byte[]> wmtsGetTiles(@PathVariable String scriptname,
                                               HttpServletRequest request,
                                               ModelMap model)
    {
        boolean debuginfo = TheConfig.getInstance().debuginfo > 0;
        if(debuginfo) System.out.println("wmtsGetTiles "+scriptname);
        StringBuilder requestURL = new StringBuilder(request.getRequestURL().toString());
        String queryString = request.getQueryString();
        if (queryString == null) {
            if(debuginfo) System.out.println( requestURL.toString() );
        } else {
            if(debuginfo) System.out.println( requestURL.append('?').append(queryString).toString());
        }
        Enumeration<String> params = request.getParameterNames();
        HashMap<String,String> lowerParams = new HashMap<String,String>();
        while (params.hasMoreElements())
        {
            String name1 = params.nextElement();
            String value1 = request.getParameter(name1) ;
            lowerParams.put( name1.toLowerCase() , value1) ;
        }
        String requeststr = lowerParams.get("request") ;//
        String servicestr = lowerParams.get("service")  ;//
        String zstr = lowerParams.get("tilematrix")  ;//
        String ystr = lowerParams.get("tilerow")  ;//
        String xstr = lowerParams.get("tilecol")  ;//
        String dtstr = lowerParams.get("datetime")  ;//
        String utimeStr = lowerParams.get("utime") ;
        String styleId = lowerParams.get("styleid") ;
        String sduiJsonStr = lowerParams.get("sdui") ;// '','null','{}' are all treated as no sdui.
        String roiid = lowerParams.get("roiid") ; // optional , null , "null" , "" are treated as no roi; good value exmaple are user:123 or sys:456.
        String scriptFilename=TheConfig.getInstance().script_dir+scriptname+".js" ;
        String fullTileKey =scriptFilename
                +","+zstr
                +","+ystr
                +","+xstr
                +","+dtstr
                +","+styleId
                +","+sduiJsonStr ;
        TileComputeResult tileResult = m_tileCache.getIfPresent(fullTileKey) ;
        if( tileResult == null ) {
            try{
                //load script contents
                String scriptContent = getScriptContent(scriptFilename);
                if( scriptContent==null ) {
                    throw new Exception("failed to load script content.");
                }
                int tilez = Integer.parseInt(zstr) ;
                int tiley = Integer.parseInt(ystr) ;
                int tilex = Integer.parseInt(xstr) ;
                if(debuginfo) System.out.println("z "+zstr+", y "+ystr+", x "+xstr);
                String extraStr = "{\"datetime\":"+dtstr+"}" ;
                //show pixelengine core version
                HBasePeHelperCppConnector cc = new HBasePeHelperCppConnector();
                if(debuginfo)System.out.println("PixelEngine Versions: " + cc.GetVersion() );
                //sdui start ---
                //检查js脚本中是否有 sdui 的声明，然后在检查GET中的sdui参数，检查GET[sdui]是否为null或者空字符串或者{}空对象
                // 前面描述的GET[sdui]均是无效的sdui，不要在js代码中附加这个sdui对象，
                // 反之，如果GET[sdui]有效的化，就把sdui={...} 写在 function main函数前面 ，后续使用AST分析进行精准替换
                if( hasSduiDeclearation(scriptContent) && sduiJsonStr!=null ){
                    if( sduiJsonStr.compareTo("null") != 0
                            && sduiJsonStr.compareTo("") != 0
                            && sduiJsonStr.compareTo("{}")!=0
                    ){
                        String sduiJsonStr2 = "\nsdui=" + sduiJsonStr + ";\n" ;
                        scriptContent = scriptContent.replace("function main(" , sduiJsonStr2 + "function main(") ;
                    }
                }
                //sdui end ===

                //Style Staff
                String styleJsonText = "" ;
                if( styleId==null || styleId.equals("") || styleId.equals("null") ) {
                    //no style
                }else{
                    byte[] ssdata = TheConfig.getInstance().getStyleData(styleId);
                    if( ssdata!=null ) {
                        styleJsonText = new String(ssdata);
                    }
                }

                //Style Staff end

                //不做裁剪直接渲染png
                TileComputeResult res1 = cc.RunScriptForTileWithRenderWithExtra(
                        "com/pixelengine/TileFilePixelEngineHelper",
                        scriptContent,
                        styleJsonText,
                        extraStr,
                        tilez,
                        tiley,
                        tilex
                ) ;
                tileResult = res1 ;
                m_tileCache.put(fullTileKey, tileResult);
                if( res1.status==0 && res1.binaryData!=null )
                {//ok
                    if(debuginfo)System.out.println("Info : tile compute ok.");

                }else
                {
                    if(debuginfo)System.out.println("Error : script wmts bad compute.");
                    final HttpHeaders headers = new HttpHeaders();
                    headers.setContentType(MediaType.TEXT_PLAIN);
                    return new ResponseEntity<byte[]>( "script wmts bad compute".getBytes(), headers, HttpStatus.NOT_FOUND);
                }
            }catch (Exception ex){
                if(debuginfo)System.out.println("Error : script wmtsGetTiles exception "  + ex.getMessage() );
                final HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.TEXT_PLAIN);
                return new ResponseEntity<byte[]>(
                        "wmtsGetTiles exception".getBytes(),
                        headers, HttpStatus.NOT_FOUND);
            }
        }
        if( tileResult!=null && tileResult.status==0 && tileResult.binaryData!=null ) {
            final HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.IMAGE_PNG);
            return new ResponseEntity<byte[]>(tileResult.binaryData, headers, HttpStatus.OK);
        }else{
            final HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.TEXT_PLAIN);
            return new ResponseEntity<byte[]>( "script wmts bad compute".getBytes(), headers, HttpStatus.NOT_FOUND);
        }
    }

    //这个方法返回二进制裸数据，数据采用BSQ波段顺序，先行后列，数据类型见dataType
    //开发人员可以根据自己需求将输出结果写出到文件或者数据库
    //wmtsGetRawData 返回裸数据，数组，不返回png图片。
    @ResponseBody
    @RequestMapping(value="/{scriptname}/rawdata/",method= RequestMethod.GET)
    @CrossOrigin(origins = "*")
    public ResponseEntity<byte[]> wmtsGetRawData(@PathVariable String scriptname,
                                               HttpServletRequest request,
                                               ModelMap model)
    {
        System.out.println("wmtsGetRawData "+scriptname);
        StringBuilder requestURL = new StringBuilder(request.getRequestURL().toString());
        String queryString = request.getQueryString();
        if (queryString == null) {
            System.out.println( requestURL.toString() );
        } else {
            System.out.println( requestURL.append('?').append(queryString).toString());
        }
        Enumeration<String> params = request.getParameterNames();
        HashMap<String,String> lowerParams = new HashMap<String,String>();
        while (params.hasMoreElements())
        {
            String name1 = params.nextElement();
            String value1 = request.getParameter(name1) ;
            lowerParams.put( name1.toLowerCase() , value1) ;
        }
        String requeststr = lowerParams.get("request") ;//
        String servicestr = lowerParams.get("service")  ;//
        String zstr = lowerParams.get("tilematrix")  ;//
        String ystr = lowerParams.get("tilerow")  ;//
        String xstr = lowerParams.get("tilecol")  ;//
        String dtstr = lowerParams.get("datetime")  ;//
        String utimeStr = lowerParams.get("utime") ;
        String styleId = lowerParams.get("styleid") ;
        String sduiJsonStr = lowerParams.get("sdui") ;// '','null','{}' are all treated as no sdui.
        String roiid = lowerParams.get("roiid") ; // optional , null , "null" , "" are treated as no roi; good value exmaple are user:123 or sys:456.
        try{
            //load script contents
            String scriptContent = null;
            scriptContentLock.lock();
            try {
                //scriptContent=TheConfig.getInstance().scriptContentDict.get(scriptname);
                if(scriptContent==null) {
                    String scriptFilename=TheConfig.getInstance().script_dir+scriptname+".js" ;
                    String tcontent = new String(
                            Files.readAllBytes(Paths.get(scriptFilename)),
                            StandardCharsets.UTF_8);
                    scriptContent = tcontent;
                    //TheConfig.getInstance().scriptContentDict.put(scriptname,tcontent);
                }
            }finally {
                scriptContentLock.unlock();
            }

            if( scriptContent==null ) {
                throw new Exception("failed to load script content.");
            }

            int tilez = Integer.parseInt(zstr) ;
            int tiley = Integer.parseInt(ystr) ;
            int tilex = Integer.parseInt(xstr) ;
            System.out.println("z "+zstr+", y "+ystr+", x "+xstr);
            String extraStr = "{\"datetime\":"+dtstr+"}" ;
            //sdui start ---
            //检查js脚本中是否有 sdui 的声明，然后在检查GET中的sdui参数，检查GET[sdui]是否为null或者空字符串或者{}空对象
            // 前面描述的GET[sdui]均是无效的sdui，不要在js代码中附加这个sdui对象，
            // 反之，如果GET[sdui]有效的化，就把sdui={...} 写在 function main函数前面 ，后续使用AST分析进行精准替换
            if( hasSduiDeclearation(scriptContent) && sduiJsonStr!=null ){
                if( sduiJsonStr.compareTo("null") != 0
                        && sduiJsonStr.compareTo("") != 0
                        && sduiJsonStr.compareTo("{}")!=0
                ){
                    String sduiJsonStr2 = "\nsdui=" + sduiJsonStr + ";\n" ;
                    scriptContent = scriptContent.replace("function main(" , sduiJsonStr2 + "function main(") ;
                }
            }
            //sdui end ===
            //show pixelengine core version
            HBasePeHelperCppConnector cc = new HBasePeHelperCppConnector();
            //不做渲染png 直接输出数组字节流。
            TileComputeResult resRaw= cc.RunScriptForTileWithoutRenderWithExtra(
                    "com/pixelengine/TileFilePixelEngineHelper",
                    scriptContent,
                    extraStr ,
                    tilez,tiley,tilex) ;
            if( resRaw.status==0 )
            {//ok
                System.out.println("Info : tile compute ok.");
                final HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
                return new ResponseEntity<byte[]>(resRaw.binaryData, headers, HttpStatus.OK);
            }else
            {
                System.out.println("Error : script wmts bad compute.");
                final HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.TEXT_PLAIN);
                return new ResponseEntity<byte[]>( "script wmts bad compute".getBytes(), headers, HttpStatus.NOT_FOUND);
            }

        }catch (Exception ex){
            System.out.println("Error : script wmtsGetTiles exception "  + ex.getMessage() );
            final HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.TEXT_PLAIN);
            return new ResponseEntity<byte[]>(
                    "wmtsGetTiles exception".getBytes(),
                    headers, HttpStatus.NOT_FOUND);
        }
    }


    @ResponseBody
    @RequestMapping(value="/invalidate/script/",method= RequestMethod.GET)
    @CrossOrigin(origins = "*")
    public ResponseEntity<byte[]> invalidateScript(@RequestParam String scriptname) {
        if( TheConfig.getInstance().debuginfo>0 ) {
            System.out.println("invalidate " + scriptname);
        }
        String scriptfullname =
                TheConfig.getInstance().script_dir + scriptname + ".js";
        //清除脚本缓存
        m_scriptCache.invalidate(scriptfullname);
        List<String> tilekeys = m_tileCache.asMap().keySet().stream()
                .filter(s->(s.startsWith(scriptfullname)))
                .collect(Collectors.toList());
        m_tileCache.invalidateAll(tilekeys);

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return new ResponseEntity<byte[]>( "{\"state\":0}".getBytes(),
                headers, HttpStatus.OK);
    }

    @ResponseBody
    @RequestMapping(value="/invalidate/productinfos",method= RequestMethod.GET)
    @CrossOrigin(origins = "*")
    public ResponseEntity<byte[]> invalidateProductinfos() {
        if( TheConfig.getInstance().debuginfo>0 ) {
            System.out.println("invalidate productinfos ");
        }
        ProductInfos.init( TheConfig.getInstance().product_infos ) ;
        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return new ResponseEntity<byte[]>( "{\"state\":0}".getBytes(),
                headers, HttpStatus.OK);
    }

    @ResponseBody
    @RequestMapping(value="/invalidate/style/",method= RequestMethod.GET)
    @CrossOrigin(origins = "*")
    public ResponseEntity<byte[]> invalidateStyle(@RequestParam String stylename) {
        if( TheConfig.getInstance().debuginfo>0 ) {
            System.out.println("invalidate " + stylename);
        }
        String styleFullname =
                TheConfig.getInstance().style_dir + stylename + ".json";
        //清除缓存
        TheConfig.getInstance().m_styleCache.invalidate(styleFullname);
        List<String> tilekeys = m_tileCache.asMap().keySet().stream()
                .filter(s->(s.contains( "styleid="+stylename )))
                .collect(Collectors.toList());
        m_tileCache.invalidateAll(tilekeys);

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return new ResponseEntity<byte[]>( "{\"state\":0}".getBytes(),
                headers, HttpStatus.OK);
    }


    ///remove \n \r space \t
    private String removeAllNoMeansChar(String text){
        String t1 = text.replace(" ","") ;
        t1 = t1.replace("\n","") ;
        t1 = t1.replace("\r","") ;
        t1 = t1.replace("\t","") ;
        return t1 ;
    }

    /// check script text if contains sdui declearation.
    private boolean hasSduiDeclearation(String jsText){
        String t1 = removeAllNoMeansChar(jsText) ;
        if( t1.contains("letsdui={") || t1.contains("varsdui={")){
            return true ;
        }else{
            return false ;
        }
    }
}
